using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Xml.Schema;

namespace ISYBAUSchemaExporter
{
    class SchemaNode
    {
        private bool mVisited;
        private XmlSchemaType mSchemaType;
        private int mPositionIndex = 0;

        public SchemaNode(bool visited, XmlSchemaType schemaType)
        {
            mVisited = visited;
            mSchemaType = schemaType;
        }

        public bool Visited
        {
            get { return mVisited; }
            set { mVisited = value; }
        }

        public XmlSchemaType SchemaType
        {
            get { return mSchemaType; }
            set { mSchemaType = value; }
        }

        public int Position
        {
            get { return mPositionIndex; }
            set { mPositionIndex = value; }
        }
    }

    class ExcelVisitor : IVisitor
    {
        private Excel.Worksheet mSheet;
        private Hashtable mSchemaTypeTable;
        private Queue<string> mTypeList;
        private int mStartIndex = 0;       

        public ExcelVisitor(Excel.Worksheet sheet)
        {
            mSheet = sheet;
            mSchemaTypeTable = new Hashtable();
            mTypeList = new Queue<string>();
            InitExcelTitle();
        }
        
        private void InitExcelTitle()
        {
            mSheet.Cells[1, 1] = "Field Name";
            mSheet.Cells[1, 2] = "Data Type";
            mSheet.Cells[1, 3] = "Field Length";
            mSheet.Cells[1, 4] = "Unit";
            mSheet.Cells[1, 5] = "Remark";
            mSheet.Cells[1, 6] = "reference";
            mSheet.get_Range("A1", "Z1").Font.Bold = true;
            mStartIndex++;
        }

        private void AddTypeTitle(int index)
        {
            mSheet.Cells[index, 1] = "Field Name";
            mSheet.Cells[index, 2] = "Data Type";
            mSheet.Cells[index, 3] = "Field Length";
            mSheet.Cells[index, 4] = "Unit";
            mSheet.Cells[index, 5] = "Remark";
            mSheet.Cells[index, 6] = "reference";

            string startCell = string.Format("A{0}", index);
            string endCell = string.Format("Z{0}", index);
            mSheet.get_Range(startCell, endCell).Font.Bold = true;

        }

        private void AddRow(int index, string name, string type, int length, string unit, string remark, string reference)
        {
            mSheet.Cells[index, 1] = name;
            mSheet.Cells[index, 2] = type;
            mSheet.Cells[index, 3] = length;
            mSheet.Cells[index, 4] = unit;
            mSheet.Cells[index, 5] = remark;
            mSheet.Cells[index, 6] = reference;
        }

        private void AddType(int index, string name, string type, string path)
        {
            mSheet.Cells[index, 1] = type;
            mSheet.Cells[index, 2] = path;
            string startIndex = string.Format("A{0}", index);
            string endIndex = string.Format("Z{0}", index);
            mSheet.get_Range(startIndex, endIndex).Font.Bold = true;
        }

        public void Visit(XmlSchema node, Object context)
        {
            foreach (XmlSchemaElement element in node.Elements.Values)
            {
                Visit(element, null);
            }
        }

        public void Visit(XmlSchemaElement node, Object context)
        {
            if (node.ElementSchemaType is XmlSchemaSimpleType)
            {
                ReadSimpleType(node, context);
            }
            else if (node.ElementSchemaType is XmlSchemaComplexType)
            {
                ReadComplexType(node, context);
            }
        }

        public void Visit(XmlSchemaParticle node, Object context)
        {
            if (node is XmlSchemaElement)
            {
                Visit(node as XmlSchemaElement, context);
            }
            else if (node is XmlSchemaGroupRef)
            {
                Visit(node as XmlSchemaGroupRef, context);
            }
            else if (node is XmlSchemaChoice)
            {
                Visit(node as XmlSchemaChoice, context);
            }
            else if (node is XmlSchemaSequence)
            {
                Visit(node as XmlSchemaSequence, context);
            }
        }

        public void Visit(XmlSchemaGroupRef node, Object context)
        {

        }

        public void Visit(XmlSchemaChoice node, Object context)
        {
            foreach (XmlSchemaObject child in node.Items)
            {
                Visit(child, context);
            }
        }

        public void Visit(XmlSchemaSequence node, Object context)
        {
            foreach (XmlSchemaObject child in node.Items)
            {
                Visit(child, context);
            }
        }

        private void Visit(XmlSchemaObject node, Object context)
        {
            if (node is XmlSchemaElement)
            {
                Visit(node as XmlSchemaElement, context);
            }
            else if (node is XmlSchemaGroupRef)
            {
                Visit(node as XmlSchemaGroupRef, context);
            }
            else if (node is XmlSchemaChoice)
            {
                Visit(node as XmlSchemaChoice, context);
            }
            else if (node is XmlSchemaSequence)
            {
                Visit(node as XmlSchemaSequence, context);
            }
        }

        private string GetAnnotation(XmlSchemaAnnotation annotation)
        {
            string result = string.Empty;
            if (annotation.Items.Count > 0)
            {
                foreach (XmlSchemaDocumentation doc in annotation.Items)
                {
                    result += doc.Markup[0].InnerText;
                }
            }

            return result;
        }

        private int GetStringLength(XmlSchemaSimpleType node)
        {
            int result = 0;
            XmlSchemaSimpleTypeRestriction content = node.Content as XmlSchemaSimpleTypeRestriction;

            if (content != null)
            {
                foreach (XmlSchemaObject item in content.Facets)
                {
                    if (item is XmlSchemaMaxLengthFacet)
                    {
                        result =Convert.ToInt32((item as XmlSchemaMaxLengthFacet).Value);
                        break;
                    }
                    else if (item is XmlSchemaLengthFacet)
                    {
                        result = Convert.ToInt32((item as XmlSchemaLengthFacet).Value);
                        break;
                    }  
                }
            }
            return result;
        }

        private void ReadSimpleType(XmlSchemaElement node, Object context)
        {
            XmlSchemaSimpleType simpleType = node.ElementSchemaType as XmlSchemaSimpleType;
            string remark = string.Empty;
            if (simpleType.Annotation != null && simpleType.Annotation.Items.Count > 0)
            {
                remark = GetAnnotation(simpleType.Annotation);
            }
            string name = node.Name;
            string type = simpleType.Name;
            if (type == null || type == string.Empty)
            {
                type = simpleType.TypeCode.ToString();
            }
            else
            {
                // this is ISYBAU customized simple type
                if (!mSchemaTypeTable.ContainsKey(type))
                {
                    mTypeList.Enqueue(type);
                    mSchemaTypeTable.Add(type, new SchemaNode(false, simpleType));
                }
            }
            int length = 0;
            if (type.ToLower() == "string")
            {
                length = GetStringLength(simpleType);
            }
            string unit = string.Empty;
            XmlSchemaSimpleTypeContent content = simpleType.Content;
            AddRow(++mStartIndex, name, type, length, unit, remark, string.Empty);
        }

        private void ReadComplexType(XmlSchemaElement node, object context)
        {          
            XmlSchemaComplexType complexNode = node.ElementSchemaType as XmlSchemaComplexType;
            if (context == null)
            {
                // start the new type
                mStartIndex = mStartIndex + 1;
                string name = node.Name;
                string type = node.SchemaTypeName.Name;
                if (type == string.Empty)
                {
                    type = string.Format("{0}[XmlComplexType]", name);
                }
                AddType(++mStartIndex, name, type, string.Empty);
                AddTypeTitle(++mStartIndex);
                Visit(complexNode.ContentTypeParticle, mStartIndex);

                while (mTypeList.Count > 0)
                {
                    string typeName = mTypeList.Dequeue();
                    SchemaNode schemaNode = mSchemaTypeTable[typeName] as SchemaNode;
                    if (!schemaNode.Visited)
                    {
                        ReadSchemaType(typeName, schemaNode.SchemaType, null);
                        schemaNode.Visited = true;
                    }
                }
            }
            else
            {
                // complextype act as a property
                string name = node.Name;
                string type = node.SchemaTypeName.Name;
                string remark = string.Empty;
                if (type == string.Empty)
                {
                    type = string.Format("{0}[XmlComplexType]", name);
                }
                if (node.Annotation != null)
                {
                    remark = GetAnnotation(node.Annotation);
                }
                if (!mSchemaTypeTable.ContainsKey(type))
                {
                    mTypeList.Enqueue(type);
                    mSchemaTypeTable.Add(type, new SchemaNode(false, complexNode));
                }
                AddRow(++mStartIndex, name, type, 0, string.Empty, remark, string.Empty);
            }
        }

        private void ReadSchemaType(string typeName, XmlSchemaType node, object context)
        {
            if (context == null)
            {                
                mStartIndex = mStartIndex + 1;
                string name = node.Name;
                string type = typeName;
                AddType(++mStartIndex, name, type, string.Empty);
                AddTypeTitle(++mStartIndex);
                if (node is XmlSchemaComplexType)
                {
                    Visit((node as XmlSchemaComplexType).ContentTypeParticle, mStartIndex);
                }
                else
                { 
                    //parse enumnation type simple type
                    XmlSchemaSimpleType simpleType = node as XmlSchemaSimpleType;
                    XmlSchemaSimpleTypeRestriction content = simpleType.Content as XmlSchemaSimpleTypeRestriction;
                    if (content != null)
                    {
                        foreach (XmlSchemaObject item in content.Facets)
                        {
                            if (item is XmlSchemaEnumerationFacet)
                            {
                                XmlSchemaEnumerationFacet itemfacet = item as XmlSchemaEnumerationFacet;
                                string itemName = itemfacet.Value;
                                string remark = GetAnnotation(itemfacet.Annotation);
                                string itemType = "[Enum]";
                                AddRow(++mStartIndex, itemName, itemType, 0, string.Empty, remark, string.Empty);
                            }
                        }
                    }
                }
            }
        }
    }
}
